---
id: tran
title: 9.26 事务和工作单元
sidebar_label: 9.26 事务和工作单元 (UnitOfWork)
---

import useBaseUrl from "@docusaurus/useBaseUrl";
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";

## 9.26.1 事务

事务指作为单个逻辑工作单元执行的一系列操作，要么**完全地执行，要么完全地不执行**。

简单的说，事务就是并发控制的单位，是用户定义的一个操作序列。 而一个逻辑工作单元要成为事务，就必须满足 `ACID` 属性。

- `A`：原子性（Atomicity）：事务中的操作要么都不做，要么就全做
- `C`：一致性（Consistency）：事务执行的结果必须是从数据库从一个一致性状态转换到另一个一致性状态
- `I`：隔离性（Isolation）：一个事务的执行不能被其他事务干扰
- `D`：持久性（Durability）：一个事务一旦提交，它对数据库中数据的改变就应该是永久性的

## 9.26.2 工作单元

简单来说，就是为了保证一次完整的功能操作所产生的一些列提交数据的完整性，起着事务的作用。在计算机领域中，工作单元通常用 `UnitOfWork` 名称表示。

通常我们保证用户的每一次请求都是处于在一个功能单元中，也就是工作单元。

## 9.26.3 如何使用

### 9.26.3.1 自动管理

在 `Furion` 框架中，我们只需要在控制器 Action 中贴 `[UnitOfWork]` 特性即可开启工作单元模式，保证了每一次请求都是一个 `工作单元`，要么同时成功，要么同时失败。

### 9.26.3.2 `EnsureTransaction()` 方法 ✨

有些时候我们通过静态类或者其他方式不小心创建了新的 `DbContext` 实例，这时候贴了 `[UnitOfWork]` 也不见起效，这时候可以通过以下方法来确认事务是否有效：

```cs
repository.EnsureTransaction();
```

**如果不喜欢手动方式也可以通过 `[UnitOfWork(true)]` 开启此功能。**

该方法会将当前仓储添加到数据库上下文池中，并确保事务可用。

### 9.26.3.2 手动管理

<Tabs
  defaultValue="one"
  values={[
    { label: "示例一", value: "one" },
    { label: "示例二", value: "two" },
    { label: "示例三（分布式）", value: "three" },
  ]}
>
  <TabItem value="one">

```cs
// 开启事务
using (var transaction = _testRepository.Database.BeginTransaction())
{
    try
    {
        _testRepository.Insert(new Blog { Url = "http://blogs.msdn.com/dotnet" });
        _testRepository.SaveNow();

        _testRepository.Insert(new Blog { Url = "http://blogs.msdn.com/visualstudio" });
        _testRepository.SaveNow();

        var blogs = _testRepository.Entity
                .OrderBy(b => b.Url)
                .ToList();

        // 提交事务
        transaction.Commit();
     }
     catch (Exception)
     {
        // 回滚事务
        // transaction.RollBack(); // 新版本自动回滚了
     }
}
```

  </TabItem>
  <TabItem value="two">

```cs
var options = new DbContextOptionsBuilder<DefaultDbContext>()
    .UseSqlServer(new SqlConnection(connectionString))
    .Options;

// 创建连接字符串
using (var context1 = new DefaultDbContext(options))
{
    // 开启事务
    using (var transaction = context1.Database.BeginTransaction())
    {
        try
        {
            _testRepository.Insert(new Blog { Url = "http://blogs.msdn.com/dotnet" });
            _testRepository.SaveNow();

            context1.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
            context1.SaveChanges();

            // 创建新的连接对象
            using (var context2 = new DefaultDbContext(options))
            {
                // 共享连接事务
                context2.Database.UseTransaction(transaction.GetDbTransaction());

                var blogs = context2.Blogs
                    .OrderBy(b => b.Url)
                    .ToList();
            }

            // 提交事务
            transaction.Commit();
        }
        catch (Exception)
        {
            // 回滚事务
            // transaction.RollBack(); // 新版本自动回滚了
        }
    }
}
```

  </TabItem>
  <TabItem value="three">

```cs {1-2}
// 开启分布式事务
using (var scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
    using (var connection = new SqlConnection(connectionString))
    {
        connection.Open();

        try
        {
            // 这里是 Ado.NET 操作
            var command = connection.CreateCommand();
            command.CommandText = "DELETE FROM dbo.Blogs";
            command.ExecuteNonQuery();

            // 创建EF Core 数据库上下文
            var options = new DbContextOptionsBuilder<BloggingContext>()
                .UseSqlServer(connection)
                .Options;
            using (var context = new BloggingContext(options))
            {
                context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
                context.SaveChanges();
            }

            // 框架封装的仓储
            _testRepository.Insert(new Blog { Url = "http://blogs.msdn.com/dotnet" });
            _testRepository.SaveChanges();

           // 提交事务
            scope.Complete();
        }
        catch (System.Exception)
        {
            // 自动回滚
        }
    }
}
```

  </TabItem>
</Tabs>

## 9.26.4 工作单元特性说明

### 9.26.4.1 `[UnitOfWork]` 特性

`[UnitOfWork]` 特性只能用于控制器的 `Action` 中，一旦贴了 `[UnitOfWork]` 特性后，那么该请求自动启用工作单元模式，要么成功，要么失败。

### 9.26.4.2 `[ManualCommit]` 特性

默认情况下，`Furion` 框架会在一次成功请求之后自动调用 `SaveChanges()` 方法，如果选择手动调用 `SaveChanges()` 方法，可以在控制器 `Action` 中贴 `[ManualCommit]` 特性即可。

## 9.26.5 反馈与建议

:::note 与我们交流

给 Furion 提 [Issue](https://gitee.com/dotnetchina/Furion/issues/new?issue)。

:::

---

:::note 了解更多

想了解更多 `事务` 知识可查阅 [EF Core - 使用事务](https://docs.microsoft.com/zh-cn/ef/core/saving/transactions) 章节。

:::
